package parallel.project.solver;

import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import parallel.project.framework.MazeException;
import parallel.project.framework.Mazemap;
import parallel.project.graph.Graph;
import parallel.project.graph.GraphException;
import parallel.project.graph.Node;
import parallel.project.solver.SlaveNotificationEvent.EventType;

public class MazeToGraphMaster implements SolverMaster 
{
	/** Mapping from a slave to the {@link Graph} it created, only 
	 * populated when a slave finishes and gives its graph */
	protected Map<MazeToGraphSlave,Graph> slaves;
	
	/** The list of all the threads */
	protected List<Thread> slaveThreads;
	
	/** All the partitions of the one maze */
	protected List<Mazemap> submazes;
	
	/** The whole maze that gets partitioned */
	protected Mazemap wholeMaze;
	
	/** Flag to indicate whether the algorithm finished or not */
	protected boolean ended;
	
	/** The final whole {@link Graph} that is created */
	protected Graph graph;
	
	/**
	 * Creates a new master, with nothing going on
	 */
	public MazeToGraphMaster()
	{
		this.slaves = new HashMap<MazeToGraphSlave,Graph>();
		this.slaveThreads = new ArrayList<Thread>();
		this.submazes = null;
		this.wholeMaze = null;
		this.ended = false;
		this.graph = null;
//		System.out.println("Master created");
	}
	
	/**
	 * Return the completed {@link Graph} if the algorithm has ended,
	 * otherwise return null.
	 * 
	 * @return
	 * 		The {@link Graph} version of the maze, or null if it hasn't
	 * 		been completed yet
	 */
	public Graph getGraph()
	{
		if (this.isEnded())
			return this.graph;
		else
			return null;
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub

	}
	
	protected void combineGraphs() throws GraphException
	{
//		System.out.println("Combining graphs");
//		if (this.slaves.size() == 1)
//			return;
		// first add all the graphs together
		Graph total = new Graph();
		for (Graph g : this.slaves.values())
			total.addGraph(g);
		
//		// remove all the start and end points cause they are in local
//		// coordinates
//		total.removeAllStartPoints();
//		total.removeAllEndPoints();
//		// set the start and end points from the original maze
//		System.out.println("start " + this.wholeMaze.getStart() +
//			" end " + this.wholeMaze.getEnd());
//		total.addStartPoint(this.wholeMaze.getStart());
//		total.addEndPoint(this.wholeMaze.getEnd());
		
		List<Graph> graphsOrdered = new ArrayList<Graph>();
		Collection<Graph> graphs = this.slaves.values();
		// go through all the mazes, which are in order of partition,
		// from right to left, thus sorting the graph sections into 
		// partition order
		for (Mazemap maze : this.submazes)
		{
			for (MazeToGraphSlave slave : this.slaves.keySet())
			{
				if (slave.getMaze().equals(maze))
				{
					graphsOrdered.add(this.slaves.get(slave));
					break;
				}
			}
		}
		/*for (int i = 0; i < graphsOrdered.size(); i++)
		{
			try {graphsOrdered.get(i).toDot("graph" + i + ".dot");}
			catch (Exception e) {e.printStackTrace();}
		}*/
		// go through the graphs and connect each with the previous by
		// seeing if there are nodes on the boundary that are right next
		// to nodes on the other boundary, which should be connected
		for (int i = 1; i < graphsOrdered.size(); i++)
		{
			Graph ithGraph = graphsOrdered.get(i);
			Graph ithMinusOneGraph = graphsOrdered.get(i - 1);
			// get all the border nodes
			int column = this.submazes.get(i).getStartingColumn();
//			System.out.println("Doing starting column " + column);
			// find all the nodes that are on that column (this graph
			// segment left boundary)
			List<Node> ithColumnNodes = 
				ithGraph.getNodesOnColumn(column);
			// find all nodes on the previous column (the previous 
			// graph segment right boundary)
			List<Node> ithMinusOneColumnNodes = 
				ithMinusOneGraph.getNodesOnColumn(column - 1);
			// go through the nodes on the current column, and for any
			// if there is a node in the previous column that is right
			// next to it (y coordinates the same), then connect them
			for (Node ithNode : ithColumnNodes)
			{
				for (Node ithMinusOneNode : ithMinusOneColumnNodes)
				{
					int ithY = ithNode.getLocation().y;
					int ithMinusOneY = ithMinusOneNode.getLocation().y;
					if (ithY == ithMinusOneY)
						total.addEdge(ithNode,ithMinusOneNode,1.0,null);
				}
			}
		}
//		System.out.println("Combining done");
//		try {total.toDot("combined.dot");}
//		catch (Exception e) {e.printStackTrace();}
//		// prune the tree
//		total.prune(this.wholeMaze.getBounds());
		// indicate that the algorithm has ended
		this.ended = true;
		// set the graph as the one that can be retrieved
		this.graph = total;
	}

	@Override
	public void slaveNotifying(SlaveNotificationEvent e)
	{
		synchronized (MazeToGraphMaster.class)
		{
			// if the slave is notifying that it make a graph
			if (e.type == EventType.GRAPH_COMPLETED)
			{
//				System.out.println("Received graph from " + 
//					((MazeToGraphSlave)e.slave).getName());
				// indicate this slave is associated with this graph
				this.slaves.put((MazeToGraphSlave)e.slave,e.graph);
				// stop the slave
				this.killSlave(e.slave);
				// if have received graphs from all the slaves, then
				// combine them all
				if (this.slaveThreads.size() == this.slaves.size())
				{
					try {this.combineGraphs();}
					catch (GraphException ge) {}
				}
			}
		}
	}

	@Override
	public void killSlave(SolverSlave slave) 
	{
		slave.stop();
	}

	@Override
	public void startSolvers(int num, Mazemap maze) throws MazeException
	{
		long sleep = 0;
		this.wholeMaze = maze;
//		System.out.println("Splitting maze");
		// split the maze into as many solvers indicated
		this.submazes = maze.splitMaze(num);
//		try {this.submazes = maze.splitMaze(num);}
//		catch (Exception e) {e.printStackTrace(); throw e;}
//		System.out.println("Maze split");
		// give the split portions to each slave and start the slaves
		for (int i = 0; i < num; i++)
		{
//			System.out.println("Making slave S" + i);
			// create slave with the submaze portion
			MazeToGraphSlave newSlave = new MazeToGraphSlave(
					"S" + i,this,this.submazes.get(i),sleep);
			// if this is the last slave, indicate that it has the last
			// maze chunk
			if (i == (num - 1))
				newSlave.setIsLastSlave(true);
			// start the slave
			Thread newSlaveThread = new Thread(newSlave);
			newSlaveThread.start();
			this.slaveThreads.add(newSlaveThread);
		}
	}

	/**
	 * Tests if the maze to graph algorithm has been completed
	 * 
	 * @return
	 * 		True if the algorithm ended, false if not
	 */
	public boolean isEnded()
	{
		return this.ended;
	}

	/**
	 * Blocks until all the slave threads have finished
	 */
	public void waitTillDone()
	{
		for (Thread thread : this.slaveThreads)
		{
			try {thread.join();}
			catch (InterruptedException ie) {ie.printStackTrace();}
		}
	}
	
	
	
}
